﻿using ContourSplit.Scaffold;
using Emgu.CV;
using Emgu.CV.Util;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ContourSplit
{
	public class ImageProcessor
	{
		//todo： threshold跟num这两个设置选项要么都从构造函数取得，要么都从config里取得。
		public int Threshold { get; set; }
		public double WidthInPX{ get; set; }
		static int thickness = 1;
		public int FindMaxAreaContourIndex(VectorOfVectorOfPoint contours, int imageArea, double ignoreRatio)
		{
			int p = 0; double maxArea = 0;
			for (int i = 0; i < contours.Size; i++)
			{
				VectorOfPoint c = contours[i];
				double area = CvInvoke.ContourArea(c);
				if (area / imageArea >= ignoreRatio) continue;
				if (area > maxArea)
				{
					maxArea = area; p = i;
					//Console.WriteLine($"maxarea: {maxArea}");
				}
			}
			return p;
		}

		public Mat ExtractMaxContour(IInputArray input, out VectorOfPoint maxContour, bool showToImage = true)
		{
			Mat copy = new Mat();
			input.GetInputArray().CopyTo(copy);
			Mat thresed = new Mat(), grey = new Mat(), blurred = new Mat();
			CvInvoke.CvtColor(copy, grey, Emgu.CV.CvEnum.ColorConversion.Rgb2Gray);

			CvInvoke.Blur(grey, blurred, new Size(15, 15), new Point(-1, -1));
			CvInvoke.Threshold(blurred, thresed, this.Threshold, 255, Emgu.CV.CvEnum.ThresholdType.Binary);

			VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
			Mat hierarchy = new Mat();
			VectorOfPoint approx = new VectorOfPoint();
			CvInvoke.FindContours(thresed, contours, hierarchy, Emgu.CV.CvEnum.RetrType.Tree, Emgu.CV.CvEnum.ChainApproxMethod.ChainApproxSimple);
			if (contours.Size > 0)
			{
				int p = FindMaxAreaContourIndex(contours, thresed.Cols * thresed.Rows, 0.9);
				CvInvoke.ApproxPolyDP(contours[p], approx, 50, true);
				maxContour = approx;
				if (showToImage)
					CvInvoke.DrawContours(copy, contours, p, new Emgu.CV.Structure.MCvScalar(255, 0, 0), thickness);
			}
			else
				maxContour = new VectorOfPoint();
			return copy;
		}

		public Mat SplitContour(IInputArray inputImage, VectorOfPoint contour, out List<Rectangle> rects, bool byVertical = true, bool showToImage = true)
		{
			Mat copy = new Mat();
			inputImage.GetInputArray().CopyTo(copy);
			Mat thresed = new Mat(), grey = new Mat(), blurred = new Mat();
			CvInvoke.CvtColor(copy, grey, Emgu.CV.CvEnum.ColorConversion.Rgb2Gray);

			CvInvoke.Blur(grey, blurred, new Size(15, 15), new Point(-1, -1));
			CvInvoke.Threshold(blurred, thresed, this.Threshold, 255, Emgu.CV.CvEnum.ThresholdType.Binary);

			Rectangle boundRect = CvInvoke.BoundingRectangle(contour);
			//CvInvoke.Rectangle(copy, boundRect, new Emgu.CV.Structure.MCvScalar(0, 255, 0), thickness);

			rects = new List<Rectangle>();
			int px = (int)(WidthInPX);
			if (px == 0)
			{
				Console.WriteLine("发生被零除错误。请合理设置单位换算。");
				return copy;
			}
			int Num = (byVertical ? boundRect.Width : boundRect.Height) / px;
			for (int i = 0; i < Num; i++)
			{
				int xy = (byVertical ? boundRect.X : boundRect.Y) + i * px;//相对于原图，第i部分左上角x/y轴坐标
				Rectangle ROIRect = new Rectangle();
				if (byVertical)
					ROIRect = new Rectangle(xy, boundRect.Y, px, boundRect.Height);
				else
					ROIRect = new Rectangle(boundRect.X, xy, boundRect.Width, px);
				Mat m = new Mat(thresed, ROIRect);
				CvInvoke.Rectangle(m, new Rectangle(new Point(0, 0), ROIRect.Size), new Emgu.CV.Structure.MCvScalar(255, 255, 255), 10);
				VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
				Mat hierarchy = new Mat();
				CvInvoke.FindContours(m, contours, hierarchy, Emgu.CV.CvEnum.RetrType.Tree, Emgu.CV.CvEnum.ChainApproxMethod.ChainApproxSimple);

				if (contours.Size > 0)
				{
					int p = FindMaxAreaContourIndex(contours, ROIRect.Width * ROIRect.Height, 0.9);

					VectorOfPoint hull = new VectorOfPoint();
					CvInvoke.ConvexHull(contours[p], hull);
					Rectangle maxRect = CGHelper.getInscribedRectangle(hull);
					Rectangle relocated = new Rectangle(new Point(maxRect.X + ROIRect.X, maxRect.Y + ROIRect.Y), maxRect.Size);
					rects.Add(relocated);
					if (showToImage)
					{
						VectorOfVectorOfPoint hulls = new VectorOfVectorOfPoint();
						var temp = hull.ToArray();
						for (int o = 0; o < temp.Length; o++)
						{
							temp[o].X += ROIRect.X;
							temp[o].Y += ROIRect.Y;
						}
						hulls.Push(new VectorOfPoint(temp));
						CvInvoke.DrawContours(copy, hulls, 0, new Emgu.CV.Structure.MCvScalar(255, 0, 0), thickness);
						CvInvoke.Rectangle(copy, relocated, new Emgu.CV.Structure.MCvScalar(0, 0, 255), thickness);
					}
				}
			}
			return copy;
		}
	}
}
